Amazon GameLift In C# 11: より良いチャットクライアントを作ります(Part2)
概要
前回の記事の続編です。
前回はGameLiftの接続担当クラスGameLiftClient.cs
のコードを分析しましたので、これから残りのChatClient.cs
とManagedConnection.cs
を解説させていただきます。
マシン環境
CPU : Intel i7-6920HQ
GPU : AMD Radeon Pro 460
Memory : 16GB
System : Windows 10 (with .NET 5 installed)
IDE : Visual Studio 2019 Community
Editor : Visual Studio Code
Terminal : Windows Terminal (or Command Prompt)
ファイルの構造とクラスの運用
このプロジェクトはGitHubにアップロードしています: AGLW-CSharp-BetterChatClientSample
ソースコードファイル :
- Program.cs (プログラムの入口)
- GameLiftClient.cs (GameLiftとの接続を管理するクラス)
- ChatClient.cs (接続できたものと受送信クラスの管理クラス)
- ManagedConnection.cs (受送信クラス)
ChatClient.cs ソースコード
ChatClient.cs
ソースコードを貼り付けます。
using System; using System.Threading; using System.Threading.Tasks; using System.Net.Sockets; namespace AGLW_CSharp_BetterChatClientSample { class ChatClient { public TcpClient ManagedConnection { get; private set; } = null; public Messenger Messenger { get; private set; } = null; public ChatClient(TcpClient client) { ManagedConnection = client; Messenger = new Messenger(ManagedConnection.GetStream()); } public void StartClient() { if (ManagedConnection != null && Messenger != null) { Messenger.StartMessenger(); } } } }
ChatClient.cs メンバー変数の解説
public TcpClient ManagedConnection { get; private set; } = null; public Messenger Messenger { get; private set; } = null;
- TcpClient :
ManagedConnection
GameLiftClient.cs
からもらったTcpClient
です。TCP通信全体を管理するメンバーです。 - Messenger :
Messenger
入力の監視、受送信の管理を担当するメンバーです。
ChatClient.cs 関数の解説
public ChatClient(TcpClient client) { ManagedConnection = client; Messenger = new Messenger(ManagedConnection.GetStream()); }
構造体は簡単です。クラスのインスタンスが作られた際、GameLiftClient
からTcpClient
を渡してくれます。ManagedConnection.GetStream()
でNetworkStream
を受け取って、Messenger
のインスタンスを作ります。
public void StartClient() { if (ManagedConnection != null && Messenger != null) { Messenger.StartMessenger(); } }
インスタンスの所有者がStartClient()
をコールします。ManagedConnection
とMessenger
両方とも存在する場合、Messenger.StartMessenger()
でMessenger
を起動します。
Messenger.cs ソースコード
Messenger.cs
ソースコードを貼り付けます。
using System; using System.Threading; using System.Net.Sockets; namespace AGLW_CSharp_BetterChatClientSample { class Messenger { public readonly System.Text.Encoding Encoder = System.Text.Encoding.UTF8; public static int SleepDuration = 100; // 100ms public static int MessageLength = 256; public NetworkStream Stream { get; private set; } = null; public Thread SenderThread { get; private set; } = null; public Thread ReceiverThread { get; private set; } = null; public string Username { get; private set; } = string.Empty; public Messenger(NetworkStream stream) { Stream = stream; } public void StartMessenger() { Console.WriteLine("Enter your username: "); Username = Console.ReadLine(); Console.Clear(); SenderThread = new Thread(() => SendMessage()); SenderThread.Start(); ReceiverThread = new Thread(() => ReceiveMessage()); ReceiverThread.Start(); } void SendMessage() { while (true) { SleepForAWhile(); MonitorInput(); } } void MonitorInput() { string text = Console.ReadLine(); string message = new string($"{Username}: {text}"); WriteMessage(Encoder.GetBytes(message)); } void WriteMessage(byte[] bytes) { ClearCurrentConsoleLine(); Stream.Write(bytes); // Console.WriteLine($"Sent : {Encoder.GetString(bytes)}"); } void ReceiveMessage() { byte[] bytes = new byte[MessageLength]; while (Stream.Read(bytes) > 0) { string text = Encoder.GetString(bytes); // Console.WriteLine($"Received : {text}"); Console.WriteLine($"{text}"); } } void SleepForAWhile() { Thread.Sleep(SleepDuration); } void ClearCurrentConsoleLine() { int currentLineCursor = Console.CursorTop; Console.SetCursorPosition(0, Console.CursorTop - 1); Console.Write(new string(' ', Console.WindowWidth)); Console.SetCursorPosition(0, currentLineCursor - 1); } } }
Messenger.cs メンバー変数の解説
public readonly System.Text.Encoding Encoder = System.Text.Encoding.UTF8; public static int SleepDuration = 100; // 100ms public static int MessageLength = 256; public NetworkStream Stream { get; private set; } = null; public Thread SenderThread { get; private set; } = null; public Thread ReceiverThread { get; private set; } = null; public string Username { get; private set; } = string.Empty;
- System.Text.Encoding :
Encoder
byte[]
(送信)とstring
(解析)を転換する担当です。 - int :
SleepDuration
とMessageLength
常に瞬時処理することは負荷が高いので、処理頻度をSleepDuration
の100ms
にします。
サンプルとして長文の処理は対応していませんので、一つメッセージの長さはMessageLength
の256bytes
にします。 - NetworkStream :
Stream
このクラスのインスタンスが作られた時点に貰った通信の通路(stream)です。 - Thread :
SenderThread
とReceiverThread
受信(SenderThread
)と送信(`ReceiverThread)処理は二つ
Thread``に分かれています。 - string :
Username
接続できてから受送信処理を起動する前に、Username
をユーザーに入力してもらいます。Username
は送信者としてメッセージの先頭に表示されます。
Messenger.cs 関数の解説
構造体は非常に簡単なので、省略いたします。
public void StartMessenger() { Console.WriteLine("Enter your username: "); Username = Console.ReadLine(); Console.Clear(); SenderThread = new Thread(() => SendMessage()); SenderThread.Start(); ReceiverThread = new Thread(() => ReceiveMessage()); ReceiverThread.Start(); }
StartMessenger()
でこのクラスを起動します。
Username = Console.ReadLine();
でユーザーネームを入力して貰ってから、送信作業SendMessage()
をSenderThread
に、受信作業ReceiveMessage()
をReceiverThread
に管理してもらいます。
void SendMessage() { while (true) { SleepForAWhile(); MonitorInput(); } } void MonitorInput() { string text = Console.ReadLine(); string message = new string($"{Username}: {text}"); WriteMessage(Encoder.GetBytes(message)); } void WriteMessage(byte[] bytes) { ClearCurrentConsoleLine(); Stream.Write(bytes); // Console.WriteLine($"Sent : {Encoder.GetString(bytes)}"); } void SleepForAWhile() { Thread.Sleep(SleepDuration); } void ClearCurrentConsoleLine() { int currentLineCursor = Console.CursorTop; Console.SetCursorPosition(0, Console.CursorTop - 1); Console.Write(new string(' ', Console.WindowWidth)); Console.SetCursorPosition(0, currentLineCursor - 1); }
SleepForAWhile()
とMonitorInput()
で100ms
毎ユーザーの入力を確認しています。
入力された情報はstring message = new string($"{Username}: {text}")
という形式、Encoder.GetBytes(message)
でbyte化してからWriteMessage(byte[] bytes)
で送信されます。
ClearCurrentConsoleLine()
は送信する直前、画面に入力したものを消して、送信したものはサーバーから返してくれます。
byte化されるメッセージはStream.Write()
を使ってTcpClient
を経由して、サーバーに送ります。
void ReceiveMessage() { byte[] bytes = new byte[MessageLength]; while (Stream.Read(bytes) > 0) { string text = Encoder.GetString(bytes); // Console.WriteLine($"Received : {text}"); Console.WriteLine($"{text}"); } }
長さはMessageLength
(256bytes)のbyte[]
バファを用意し、while (Stream.Read(bytes) > 0)
で常に受信確認を行っています。
受信できたものをbyte[]
からstring
に転換し、Console.WriteLine($"{text}")
で画面にプリントします。
最後
Cognito記事のようにAmazon Cognitoを用意してから、アプリのビルドとAmazon GameLiftにアップロード手順で前に作ったBetterChatServerをアップロードします。
サーバー部分が準備できてからこのクライアントプログラムを起動すると、普通にチャットできる状態になります。